/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <baja/graphics/dx11.1/details/common.hpp>
#include <baja/graphics/dx11.1/graphics_fwd.hpp>
#include <baja/graphics/dx11.1/details/enum_mappings.hpp>

namespace baja { namespace graphics { namespace dx11_1 {

class asset_manager : public iasset_manager 
{
private:
    template <typename T> class asset_collection
    {
    public:
        asset_collection() {}

        // adds an asset to the collection
        void addItem(__in const wchar* assetName, const std::shared_ptr<T>& item)
        {
            BAJA_CHECK_ARG(assetName);
            BAJA_CHECK_ARG(item.get());

            if (_assetMap.find(assetName) != _assetMap.end())
            {
                BAJA_THROW(std::logic_error("item already exists")); // ERROR:
            }
            else
            {
                _assetMap[assetName] = item;
            }
        }

        // gets an asset by name
        std::shared_ptr<T> getItem(const wchar* assetName, bool noThrow = false) const
        {
            BAJA_CHECK_ARG(assetName);

            auto found = _assetMap.find(assetName);
            if (found == _assetMap.end())
            {
                if (!noThrow)
                    BAJA_THROW(std::logic_error("item doesn't exist in the collection")); // ERROR: 
            }
            else
            {
                return found->second;
            }

            return std::shared_ptr<T>();
        }

        std::shared_ptr<T> tryGetItem(const wchar* assetName) const
        {
            return this->getItem(assetName, true); // noThrow
        }

        
        // gets count of items in the collection
        uint32 getAssetCount() const
        {
            return _assetMap.size();
        }

    private:
        std::map<std::wstring, std::shared_ptr<T>> _assetMap; 
    };

public:
    asset_manager() {}

    static std::shared_ptr<asset_manager> create()
    {
        return std::make_shared<asset_manager>();
    }

    // gets or creates a font from the specified properties
    std::shared_ptr<ifont> getOrCreateFont(
        const std::shared_ptr<iengine>& graphics,
        const wchar* fontName,
        const uint32 fontHeight,
        const bool rightToLeft
        )
    {
        BAJA_CHECK_ARG(fontName);

        std::shared_ptr<ifont> returnValue;

        // attempt to get the font from the collection
        returnValue = _fontCollection.tryGetItem(fontName);

        // check if the collection contained a matching font
        if (returnValue.get() == nullptr)
        {
            // the font is not in the collection, so create it
            returnValue = createFont(graphics, fontName, fontHeight, rightToLeft);
             _fontCollection.addItem(fontName, returnValue);
        }
     
        return returnValue;
    }

    std::recursive_mutex _assetMutex;

    std::shared_ptr<ieffect2> addEffect(
        const std::shared_ptr<iengine>& graphics, 
        const std::shared_ptr<effect2_description>& desc
        )
    {
        BAJA_CHECK_ARG(graphics.get());
        BAJA_CHECK_ARG(desc->name);

        BAJA_LOCK(_assetMutex);

        // make sure it's not already in the collection
        BAJA_VERIFY(!this->getEffect(desc->name));

        std::shared_ptr<ieffect2> returnValue = graphics->createEffect(desc);
        _effect2Collection.addItem(desc->name, returnValue);

        return returnValue;
    }

    std::shared_ptr<ieffect2> getOrCreateEffect(
        const std::shared_ptr<iengine>& graphics,
        const std::shared_ptr<effect2_description>& desc
        )
    {
        BAJA_CHECK_ARG(graphics.get());
        BAJA_CHECK_ARG(desc->name);

        BAJA_LOCK(_assetMutex);

        std::shared_ptr<ieffect2> returnValue = this->getEffect(desc->name);

        if (!returnValue)
        {
            returnValue = this->addEffect(graphics, desc);
        }
        
        return returnValue;
    }

    // gets an effect, if it exists in the asset list
    std::shared_ptr<ieffect2> getEffect(const wchar* effectName)
    {
        BAJA_CHECK_ARG(effectName);
        return _effect2Collection.getItem(effectName, true); // nothrow
    }

    // gets or creates a texture from file
    std::shared_ptr<itexture> getOrCreateTextureFromFile(
        const std::shared_ptr<iengine>& graphics,
        const wchar* filename,
        const pixel_format format
        )
    {
        BAJA_CHECK_ARG(graphics.get());
        BAJA_CHECK_ARG(filename);

        std::shared_ptr<itexture> returnValue;
        returnValue = _textureCollection.tryGetItem(filename); // NOTE: this was cast to SAFEHR

        if (returnValue.get() == nullptr)
        {
            // NOTE: this used to automatically search in a path but now filename is supposed to contain the full path
            returnValue = graphics->createTextureFromFile(
                filename,
                0, // usage
                pixel_format::fromSource,
                memory_pool::default,
                mipmap_load::forceMipmaps
                );

            _textureCollection.addItem(filename, returnValue);
        }

        return returnValue;
    }

    void addTexture(const wchar* filename, const std::shared_ptr<g::itexture>& texture)
    {
        _textureCollection.addItem(filename, texture);
    }

    std::shared_ptr<itexture> getTexture(const wchar* filename)
    {
        BAJA_CHECK_ARG(filename);
        return _textureCollection.getItem(filename, true); // nothrow
    }

    /*
    // NYI:
    //
    // gets or creates a texture from one of our resource files
    //
    HRESULT __stdcall GetOrCreateTextureFromResourceFile(
        IGxEngine* graphics,
        const wchar* filename,
        GxPixelFormatEnum format,
        __out IGxTexture** texture
        )
    {
        TRACEHR hr = S_OK;
        
        if (SUCCEEDED(hr))
        {
            stringw assetPath;
            path::get_module_path(assetPath);
            assetPath.append(filename);

            hr = this->GetOrCreateTextureFromFile(graphics, assetPath.c_str(), format, texture);
        }

        return hr;
    }
    */
    
    // adds a mesh asset to be managed
    void addMesh(
        const wchar* meshName,
        const std::shared_ptr<imesh>& mesh
        )
    {
        BAJA_CHECK_ARG(meshName);
        BAJA_CHECK_ARG(mesh.get());
        _meshCollection.addItem(meshName, mesh);
    }

    // gets a mesh asset by name
    std::shared_ptr<imesh> getMesh(const wchar* meshName)
    {
        BAJA_CHECK_ARG(meshName);
        return _meshCollection.getItem(meshName, true); // nothrow
    }

    // adds a vertex declaration to be managed
    void addVertexDeclaration(
        const wchar* vertexDeclarationName,
        const std::shared_ptr<ivertex_declaration>& vertexDeclaration
        )
    {
        BAJA_CHECK_ARG(vertexDeclarationName);
        BAJA_CHECK_ARG(vertexDeclaration.get());

        BAJA_LOCK(_assetMutex);

        if (_vertexDeclCollection.tryGetItem(vertexDeclarationName).get() == nullptr)
        {
            _vertexDeclCollection.addItem(vertexDeclarationName, vertexDeclaration);
        }
    }

    // gets a vertex declaration by name
    std::shared_ptr<ivertex_declaration> getVertexDeclaration(const wchar* vertexDeclarationName, bool noThrow)
    {
        BAJA_CHECK_ARG(vertexDeclarationName);
        return _vertexDeclCollection.getItem(vertexDeclarationName, noThrow);
    }

private:
    asset_collection<ifont> _fontCollection;
    asset_collection<itexture> _textureCollection;
    asset_collection<ieffect2> _effect2Collection;
    asset_collection<imesh> _meshCollection;
    asset_collection<ivertex_declaration> _vertexDeclCollection;
};

}}} // namespace baja.graphics.dx11_1

namespace baja { namespace graphics {

// Creates a asset_manager object
inline std::shared_ptr<iasset_manager> createAssetManager()
{
    std::shared_ptr<::baja::graphics::dx11_1::asset_manager> returnValue = ::baja::graphics::dx11_1::asset_manager::create();
    return std::dynamic_pointer_cast<iasset_manager>(returnValue);
}

}} // namespace baja.graphics

